package neuron;

import common.statistics.Series;
import java.util.ArrayList;
import java.util.List;
import neuron.geometry.Transform;
import optimization.LinearAlgebra;

public class NeuronStatistics {

	Neuron n;
	
	
	public NeuronStatistics(Neuron n) {
		this.n = n;
	}
	
	public Series basalTermSegCountDist()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.add(stat.countTerminalSegments());
		}
		return s;
	}

	public Series basalDendriteLengthDist()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.add(stat.totalLength());
		}
		return s;
	}
	
	public Series basalAsymIndexDist()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.add(stat.asymmetricIndex());
		}
		return s;
	}
	
	public Series basalTermSegLenDist()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.merge(stat.terminalSegmentLengths());
		}
		return s;
	}

	public Series basalIntSegLenDist()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.merge(stat.intermediateSegmentLengths());
		}
		return s;
	}

	public Series obliqueTermSegCountDist()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.add(stat.countTerminalSegments());
		}
		return s;
	}

	public Series obliqueAsymIndexDist()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.add(stat.asymmetricIndex());
		}
		return s;
	}

	public Series tuftTermSegCountDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite().getTuftDendrite();
		Statistics stat = new Statistics(d.root);
		s.add(stat.countTerminalSegments());
		return s;
	}

	public Series tuftAsymIndexDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite().getTuftDendrite();
		Statistics stat = new Statistics(d.root);
		s.add(stat.asymmetricIndex());
		return s;
	}

	public double apicalTermSegCount()
	{
		Statistics stat = new Statistics(n.getApicalDendrite().root);
		return stat.countTerminalSegments();
	}

	public double apicalAsymIndex()
	{
		Statistics stat = new Statistics(n.getApicalDendrite().root);
		return stat.asymmetricIndex();
	}

	public Series apicalTermSegLenDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite();
		Statistics stat = new Statistics(d.root);
		s.merge(stat.terminalSegmentLengths());
		return s;
	}

	public Series apicalIntSegLenDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite();
		Statistics stat = new Statistics(d.root);
		s.merge(stat.intermediateSegmentLengths());
		return s;
	}

	public Series obliqueTermSegLenDist()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.merge(stat.terminalSegmentLengths());
		}
		return s;
	}

	public Series obliqueIntSegLenDist()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			Statistics stat = new Statistics(d.root);
			s.merge(stat.intermediateSegmentLengths());
		}
		return s;
	}

	public Series tuftTermSegLenDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite().getTuftDendrite();
		Statistics stat = new Statistics(d.root);
		s.merge(stat.terminalSegmentLengths());
		return s;
	}

	public Series tuftIntSegLenDist()
	{
		Series s = new Series();
		Dendrite d = n.getApicalDendrite().getTuftDendrite();
		Statistics stat = new Statistics(d.root);
		s.merge(stat.intermediateSegmentLengths());
		return s;
	}

	private int[] sholl(double delta, double maxR, List<Coord> coords)
	{
		Coord origo = new Coord(0,0,0,0);
		int K = (int) Math.ceil(maxR / delta);
		int sholl[] = new int[K];
		int i = 0;
		for (double r = 0; r < maxR; r+= delta) {
			double r2 = r + delta;
			for (Coord c : coords) {
				double dist = c.distanceTo(origo); // assume soma == origo
				if (r < dist && dist < r2) {
					sholl[i]++;
				}
			}
			i++;
		}
		return sholl;		
	}

	/**
	 * Compute Sholl analysis for basal dendrites
	 * @param delta
	 * @return
	 */
	public int[] basalSholl(double delta)
	{
		List<Coord> coords = new ArrayList<Coord>();
		Transform.translateToOrigo(n);
		for (Dendrite d : n.basalDendrites()) {
			List<Segment> segs = new ArrayList<Segment>();
			d.root.collectSegments(segs);
			for (Segment s : segs) {
				if (!s.getPoints().isEmpty()) {
					coords.add(s.lastCoordinate());
				}
			}
		}
		return sholl(delta, 500, coords);
	}

	/**
	 * Compute Sholl analysis for all dendrites
	 * @param delta
	 * @return
	 */
	public int[] neuronSholl(double delta)
	{
		List<Coord> coords = new ArrayList<Coord>();
		for (Dendrite d : n.allDendrites()) {
			List<Segment> segs = new ArrayList<Segment>();
			d.root.collectSegments(segs);
			for (Segment s : segs) {
				if (!s.getPoints().isEmpty()) {
					coords.add(s.lastCoordinate());
				}
			}
		}
		return sholl(delta, 1500, coords);
	}

	/**
	 * Return branching angle for segments beginning from s
	 * @param s
	 * @param ser
	 */
	private void getBranchAngles(Segment s, Series ser)
	{
		if (!s.isBranch()) return;

		for (Segment ns : s.getBranches()) {
			getBranchAngles(ns, ser);
		}

		Coord last = s.lastCoordinate();
		if (last != null) {
			if (s.getBranch(0) == null) { System.out.println("*** ERROR *** Segment marked as branch but no 0th branch segment!"); return; }
			if (s.getBranch(1) == null) { System.out.println("*** ERROR *** Segment marked as branch but no 1st branch segment!"); return; }
			Coord b1f = s.getBranch(0).firstCoordinate();
			Coord b2f = s.getBranch(1).firstCoordinate();
			double[] v1 = LinearAlgebra.vSub(b1f.asArray(), last.asArray());
			double[] v2 = LinearAlgebra.vSub(b2f.asArray(), last.asArray());
			double dot = LinearAlgebra.vMul(v1, v2);
			double lenprod = LinearAlgebra.vNorm2(v1) * LinearAlgebra.vNorm2(v2);
			double angle = Math.acos(dot / lenprod);
			if (!Double.isNaN(angle)) ser.add(angle);
		}

	}

	/**
	 * Return basal branching angles
	 * @return
	 */
	public Series basalBranchAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			getBranchAngles(d.root, s);
		}
		return s;
	}

	public Series obliqueBranchAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			getBranchAngles(d.root, s);
		}
		return s;
	}

	public Series tuftBranchAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getTuftDendriteAsList()) {
			getBranchAngles(d.root, s);
		}
		return s;
	}

		/**
	 * Return turning angle for segments beginning from s
	 * @param s
	 * @param ser
	 */
	private void getTurningAngles(Segment s, Series ser)
	{
		Coord prev = null, prev2 = null;
		for (Coord c : s.getPoints()) {
			if (prev == null) { prev = c; continue; }
			if (prev2 == null) { prev2 = prev; prev = c; continue; }

			double[] v1 = LinearAlgebra.vSub(prev.asArray(), prev2.asArray());
			double[] v2 = LinearAlgebra.vSub(c.asArray(), prev.asArray());

			double dot = LinearAlgebra.vMul(v1, v2);
			double lenprod = LinearAlgebra.vNorm2(v1) * LinearAlgebra.vNorm2(v2);
			double angle = Math.acos(dot / lenprod);
			if (!Double.isNaN(angle)) ser.add(angle);
		}

		for (Segment ns : s.getBranches()) {
			getTurningAngles(ns, ser);
		}
	}

	/**
	 * Return basal turning angles
	 * @return
	 */
	public Series basalTurningAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.basalDendrites()) {
			getTurningAngles(d.root, s);
		}
		return s;
	}

	public Series obliqueTurningAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getObliqueDendrites()) {
			getTurningAngles(d.root, s);
		}
		return s;
	}

	public Series tuftTurningAngles()
	{
		Series s = new Series();
		for (Dendrite d : n.getApicalDendrite().getTuftDendriteAsList()) {
			getTurningAngles(d.root, s);
		}
		return s;
	}
}
